﻿using UnityMethods.Extend;
using log4net;
using log4net.Config;
using log4net.Repository;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using SqlSugar;
using Swashbuckle.AspNetCore.Filters;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using UnityTemplate.API.Filter;
using UnityTemplate.API.LogHelper;
using UnityTemplate.ApiModel;
using Asp.Versioning;

namespace UnityTemplate.API
{
    public class Startup
    {
        protected ISqlSugarClient Db { get; set; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        /// <summary>
        /// log4net 仓储库
        /// </summary>
        public static ILoggerRepository LogRepository { get; set; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            string baseDir = Path.GetDirectoryName(typeof(Program).Assembly.Location);

            //log注入ILoggerHelper
            services.AddSingleton<ILoggerHelper, LoggerHelper>();

            //log4net
            LogRepository = LogManager.CreateRepository("UnityTemplate.API");
            XmlConfigurator.Configure(LogRepository, new FileInfo(Path.Combine(baseDir, "Configs/log4net.config")));

            //services.AddHttpContextAccessor();

            #region Dependency Injection Services

            //Load assembly from appsetting.json
            //
            string assemblies = Configuration["Assembly:FunctionAssembly"];

            if (!string.IsNullOrEmpty(assemblies))
            {
                foreach (var item in assemblies.Split('|'))
                {
                    Assembly assembly = Assembly.Load(item);
                    foreach (var implement in assembly.GetTypes())
                    {
                        Type[] interfaceType = implement.GetInterfaces();
                        foreach (var service in interfaceType)
                        {
                            services.AddTransient(service, implement);
                        }
                    }
                }
            }

            #endregion

            services.AddMemoryCache();//添加基于内存的缓存支持
            services.AddControllers();

            #region Swagger配置

            services.AddSwaggerGen(option =>
            {
                option.SwaggerDoc("v1", new OpenApiInfo { Title = "UnityTemplate.API", Version = "v1" });
                option.IncludeXmlComments(Path.Combine(baseDir, "UnityTemplate.API.xml"));
                option.IncludeXmlComments(Path.Combine(baseDir, "UnityTemplate.ApiModel.xml"));

                // 开启加权小锁
                option.OperationFilter<AddResponseHeadersFilter>();
                option.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
                // 在header中添加token，传递到后台
                option.OperationFilter<SecurityRequirementsOperationFilter>();

                // Jwt Bearer 认证
                option.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
                {
                    Name = "Authorization",//jwt默认的参数名称
                    In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
                    Type = SecuritySchemeType.ApiKey,
                    Description = "Authorization:Bearer {your JWT token}，注意两者之间是一个空格",
                });
            });

            #endregion

            #region JWT认证

            services.Configure<JwtConfig>(Configuration.GetSection("JwtConfig"));

            string issuer = Configuration["JwtConfig:Issuer"];
            string audience = Configuration["JwtConfig:Audience"];
            string expire = Configuration["JwtConfig:ExpireHours"];
            TimeSpan expiration = TimeSpan.FromHours(Convert.ToDouble(expire));
            //配置JWT授权
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(jwt =>
            {
                jwt.SaveToken = true;
                jwt.TokenValidationParameters = new TokenValidationParameters
                {
                    /*这将使用我们在 appsettings 中添加的 secret 来验证 JWT token 的第三部分，并验证 JWT token 是由我们生成的*/
                    ValidateIssuerSigningKey = true,
                    /*将密钥添加到我们的 JWT 加密算法中*/
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["JwtConfig:SecurityKey"])),
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ValidateLifetime = true,
                    RequireExpirationTime = true,
                    ValidIssuer = issuer,
                    ValidAudience = audience,
                    ClockSkew = expiration,
                };

                jwt.Events = new JwtBearerEvents
                {
                    //此处为权限验证失败后触发的事件
                    OnChallenge = context =>
                    {
                        /*终止.Net Core默认的返回类型和数据结果，必须*/
                        context.HandleResponse();

                        context.Response.ContentType = "application/json";
                        context.Response.StatusCode = StatusCodes.Status200OK;

                        string error = context.Error;
                        if (!string.IsNullOrWhiteSpace(context.ErrorDescription))
                        {
                            error = $"{context.Error}({context.ErrorDescription})";
                        }

                        if (error.IsEmpty())
                        {
                            error = "Token can not empty";
                        }

                        context.Response.WriteAsync(JsonConvert.SerializeObject(BaseResponse.BadRequest(error, StatusCodes.Status401Unauthorized)));
                        return Task.FromResult(0);
                    }
                };

            });
            services.AddAuthorization(options =>
            {
                options.AddPolicy("Client", policy => policy.RequireRole("Client"));
            });
            #endregion

            services.Configure<ApiBehaviorOptions>(opt => opt.SuppressModelStateInvalidFilter = true);
            services.AddMvc(options =>
            {
                options.Filters.Add<ValidateModelAttribute>();
                options.Filters.Add<ApiResultFilterAttribute>();
                options.Filters.Add<GlobalExceptionFilter>();
            });

            /*添加接口版本控制*/
            services.AddApiVersioning(options =>
            {
                options.ReportApiVersions = true;
                options.AssumeDefaultVersionWhenUnspecified = true;
                options.DefaultApiVersion = new ApiVersion(1, 0);
            });

            /*注入SQLSugar，多实例，单例使用 AddSingleton
             https://www.donet5.com/home/Doc?typeId=2247
            */
            Db = services.AddSqlsugarSetup(Configuration, "SugarConnectString");
            InitDataTable(Path.Combine(baseDir, "UnityTemplate.Repository.dll"));
            //Db.CodeFirst.SetStringDefaultLength(200).InitTables(typeof(Db_BaseUserInfo));
            //services.AddGrpc();
            //services.AddScoped<IGreeterService, GreeterService>();

            Assembly asm = Assembly.GetAssembly(typeof(UnityTemplate.API.Startup));

            //var controlleractionlist = asm.GetTypes()
            //        .Where(type => typeof(UnityTemplate.API.Controllers.BaseApiController).IsAssignableFrom(type))
            //        .SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public))
            //        .Where(m => !m.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true).Any())
            //        .Select(x => new { Controller = x.DeclaringType.Name, Action = x.Name, ReturnType = x.ReturnType.Name, Attributes = String.Join(",", x.GetCustomAttributes().Select(a => a.GetType().Name.Replace("Attribute", ""))) })
            //        .OrderBy(x => x.Controller).ThenBy(x => x.Action).ToList();
        }

        /// <summary>
        /// 初始化数据库表
        /// </summary>
        /// <param name="path"></param>
        private void InitDataTable(string path)
        {
            /* 批量创建表 demo:https://www.donet5.com/home/doc?masterId=1&typeId=1206 */
            Type[] types = Assembly.LoadFrom(path).GetTypes().Where(it => Attribute.IsDefined(it, typeof(SugarTable))).ToArray();
            Db.CodeFirst.SetStringDefaultLength(200).InitTables(types);//根据types创建表
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "UnityTemplate.API v1"));
                app.UseMiddleware<RequestResponseLoggingMiddleware>();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            /*注意开启认证授权的顺序*/
            app.UseAuthorization(); /*角色身份认证策略*/
            app.UseAuthentication(); /*身份认证类型*/

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();

                //endpoints.MapGrpcService<GreeterService>();                
            });
        }


    }
}
